import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from sklearn.datasets import load_iris

plt.rcParams['font.sans-serif'] = ['SimHei']     # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False       # 正常显示负号

# 加载鸢尾花数据
data = load_iris()
X = data.data[:, :2]  # 使用前两个特征
y = data.target

# 仅使用类别0（Setosa）和类别1（Versicolor）
X = X[y != 2]
y = y[y != 2]

# 标签变为 -1 和 +1
y = np.where(y == 0, -1, 1)

# 手写感知机类
class Perceptron:
    def __init__(self, lr=0.01, n_iters=1000):
        self.lr = lr
        self.n_iters = n_iters
        self.weights = None
        self.bias = None
        self.history = []  # 每次更新记录权重和偏置

    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.weights = np.zeros(n_features)
        self.bias = 0

        for _ in range(self.n_iters):
            for xi, yi in zip(X, y):
                linear_output = np.dot(xi, self.weights) + self.bias
                y_pred = np.sign(linear_output)
                if yi * y_pred <= 0:
                    self.weights += self.lr * yi * xi
                    self.bias += self.lr * yi
                    # 保存当前状态
                    self.history.append((self.weights.copy(), self.bias))

    def predict(self, X):
        linear_output = np.dot(X, self.weights) + self.bias
        return np.sign(linear_output)

# 初始化并训练模型
model = Perceptron(lr=0.1, n_iters=150)
model.fit(X, y)

# 预测与准确率
y_pred = model.predict(X)
accuracy = np.mean(y_pred == y)
print(f"准确率: {accuracy * 100:.2f}%")

# 绘图函数：决策边界
def plot_decision_boundary(X, y, model):
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
                         np.linspace(y_min, y_max, 200))
    grid = np.c_[xx.ravel(), yy.ravel()]
    Z = model.predict(grid).reshape(xx.shape)

    plt.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm')
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k')
    plt.xlabel("Sepal length")
    plt.ylabel("Sepal width")
    plt.title("感知机决策边界 (Setosa vs Versicolor)")
    plt.show()

plot_decision_boundary(X, y, model)

def animate_training(X, y, history, save_path='perceptron_training.gif'):
    fig, ax = plt.subplots()
    plt.xlabel("Sepal length")
    plt.ylabel("Sepal width")
    plt.title("感知机训练过程 - 分界线动态演化")

    scatter = ax.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k')

    x_vals = np.linspace(X[:, 0].min()-1, X[:, 0].max()+1, 100)
    line, = ax.plot([], [], 'k--')

    def update(frame):
        w, b = history[frame]
        # 画出当前权重对应的分界线: w1*x + w2*y + b = 0 => y = -(w1*x + b)/w2
        if w[1] != 0:
            y_vals = -(w[0] * x_vals + b) / w[1]
        else:
            y_vals = np.zeros_like(x_vals)
        line.set_data(x_vals, y_vals)
        ax.set_title(f"训练步数：{frame + 1}")
        return line,

    anim = FuncAnimation(fig, update, frames=len(history), interval=200, repeat=False)
    anim.save(save_path, writer='pillow')
    print(f"动画已保存为 {save_path}")
    plt.show()

# 动画显示训练过程
animate_training(X, y, model.history, save_path="perceptron_training.gif")
